package storageManager;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;


public class IndexFile extends FileClass{
	//public RandomAccessFile raf;
	public TableRecord tableRecord;
	public int sizeOfAnIndexRecord;
	public int totalIndexRecord;
	public int sizeOfTheKeyValue;
	public IndexFile(TableRecord record) throws IOException{
		this.tableRecord = record;
		this.sizeOfAnIndexRecord = record.getSizeOfAnIndexRecord();
		this.totalIndexRecord = record.getNumberOfTotalIndexRecord();
		this.sizeOfTheKeyValue = record.getFieldSizes().get(0);
//		this.raf = new RandomAccessFile(tableRecord.getTableName()+"in.txt", "rw");
		this.raf = new RandomAccessFile(tableRecord.getTableName()+".in", "rw");
		if(size()==0){
			createAPage();
		}
	}
	public byte[] maxString(){//Decides the maxString according to the type of the keyValue
		byte[] max = new byte[sizeOfTheKeyValue];
		for(int i=0; i<sizeOfTheKeyValue; i++){
			max[i] = (byte) 127;
		}
		return max;
	}
	public void createAPage() throws IOException{//Creates a new page by writing the page header only.
		int offset = numberOfPages()*512;
		raf.seek(offset);
		raf.write(maxString());
		raf.write(tableRecord.getNumberOfTotalIndexRecord());
	}
	public IndexPageAndPageNumber findProperPageHelper(String s) throws IOException{//This method finds the page that might have an index record with the given key value and its page number
		String pageHeader;
		int pageNumber = numberOfPages();
		for(int i=0;i<pageNumber;i++){
			IndexPage thePage = byteToPage(i);//Gets the page
			pageHeader = thePage.getMaxKeyValue();
			if(Tools.isNumeric(s) && Tools.isNumeric(pageHeader)){//If the key value and pageheader are both numeric
				if(Double.parseDouble(s)-Double.parseDouble(pageHeader)<=0.0){//It compares them as integers
					return (new IndexPageAndPageNumber(thePage,i));//It returns an object that has the index object and its page number
				}
			}else if(Tools.isNumeric(s) && !Tools.isNumeric(pageHeader)){//If only one of them is numeric it puts the numeric one front
				return (new IndexPageAndPageNumber(thePage,i));
			}else if(!Tools.isNumeric(s) && Tools.isNumeric(pageHeader)){
				continue;
			}else{//If none of them numeric it makes a string comparison
				if(s.compareTo(pageHeader)<=0){
					return (new IndexPageAndPageNumber(thePage,i));
				}
			}
		}
		return null;//Otherwise it returns null. This means that givenKeyValue is greater than every record's keyValue
	}
	public IndexPageAndPageNumber findTheProperPageToWrite(String s) throws IOException{//This method decides what page to write the record
		IndexPageAndPageNumber pageHelper = findProperPageHelper(s);//Gets help from the helper to know where to write
		int pageNumber = numberOfPages();
		if(pageHelper!=null){//If helper is not null this means that there is at least one record that has greater keyValue than the given keyValue, it returns the the method above.
			return pageHelper; 
		}
		IndexPage lastPage = byteToPage(pageNumber-1);//If it is not it gets the last page as an object
		if(lastPage.hasAvailableSpace()){//Checks if it has available space.
			return new IndexPageAndPageNumber(lastPage, pageNumber-1);//If there is returns the last page and the number of the last page
		}
		return null;//Otherwise returns the null
	}
	public ArrayList<IndexRecord> listOfIndexrecords() throws IOException{//Returns every index record in the index file
		ArrayList<IndexRecord> result = new ArrayList<IndexRecord>();
		for(int i=0;i<numberOfPages();i++){
			IndexPage thePage = byteToPage(i);
			result.addAll(thePage.getIndexRecords());
		}
		return result;
	}
	public IndexRecord findTheRecord(String keyValue) throws IOException{//Finds and returns the index record with the given keyValue
		IndexPageAndPageNumber inPageAndNumber = findProperPageHelper(keyValue);
		IndexPage thePage = inPageAndNumber.getIndexPage();
		return thePage.findTheRecord(keyValue);
	}
	public ArrayList<IndexRecord> smallerThan(String value) throws IOException{//Returns the index records that has smaller keyValues than the given keyValue
		ArrayList<IndexRecord> result = new ArrayList<IndexRecord>();
		IndexPageAndPageNumber inPageAndNumber = findProperPageHelper(value);
		if(inPageAndNumber==null){
			return listOfIndexrecords();
		}
		int pageNumber = inPageAndNumber.getPageNumber();//findProperPageHelper(value);
		for(int i=0;i<pageNumber;i++){
			IndexPage thePage = byteToPage(i);
			result.addAll(thePage.getIndexRecords());
		}
		result.addAll(inPageAndNumber.getIndexPage().smallerThan(value));
		return result;
	}
	public ArrayList<Integer> deleteIndexRecord(String keyValue) throws IOException{//Physically deletes the index record and returns the pageNumber and recordNumber of the deleted index record to logically delete corresponding data record.
		ArrayList<Integer> result = new ArrayList<Integer>();
		IndexPageAndPageNumber inPageAndNumber = findProperPageHelper(keyValue);
		int pageOfRecord = inPageAndNumber.getPageNumber();
		IndexPage page = inPageAndNumber.getIndexPage();
		IndexRecord deletedRecord = page.removeRecord(keyValue);
		result.add(deletedRecord.getPageNumber());
		result.add(deletedRecord.getRecordNumber());
		int i;
		int numOfPages = numberOfPages();
		for(i=pageOfRecord;i<numOfPages-1;i++){
			IndexPage secondPage = byteToPage(i+1);
			page = addFirstRecordFromTheNextPage(page,secondPage,i);
		}
		raf.seek(i*512);
		raf.write(page.toByte());
		return result;
	}
	public IndexPage addFirstRecordFromTheNextPage(IndexPage firstPage, IndexPage secondPage,int pageNumber) throws IOException{//Adds the first index record from the secondPage to firstPage
		if(secondPage.getNumberOfAvailableRecords()<totalIndexRecord){
			IndexRecord theRecord = secondPage.getFirstIndexRecord();
			firstPage.addIndexRecord(theRecord);
		}
		raf.seek(pageNumber*512);
		raf.write(firstPage.toByte());
		return secondPage;
	}
	public void addIndexrecord(IndexRecord indexRecord) throws IOException{//Adds the given indexRecord to the proper page
		IndexPageAndPageNumber thePageAndPageNumber = findTheProperPageToWrite(indexRecord.getKeyValue());
		IndexPage thePage=null;
		if(thePageAndPageNumber==null){
			createAPage();
			thePage = byteToPage(numberOfPages()-1);
		}else{
			thePage = thePageAndPageNumber.getIndexPage();
		}
		thePage.addIndexRecord(indexRecord);
		if(thePage.getIndexRecords().size()>totalIndexRecord){
			IndexRecord recordToNextPage = thePage.getLastIndexRecord();
			writeThePage(thePageAndPageNumber.getPageNumber(), thePage);
			addIndexrecord(recordToNextPage);
		}else{
			writeThePage(thePageAndPageNumber.getPageNumber(), thePage);
		}		
	}
	public boolean isInTable(String keyValue) throws IOException{//Returns true if a record with the given keyValue exists in the file
		IndexPageAndPageNumber inPageAndNumber = findProperPageHelper(keyValue);
		if(inPageAndNumber==null){
			return false;
		}
		IndexPage thePage = inPageAndNumber.getIndexPage();
		return thePage.isInPage(keyValue);
	}
	@Override
	public IndexRecord byteToRecord(byte[] bytes) {//Parse the byte array to an index record object
		IndexRecord result = new IndexRecord();
		result.setKeyValue(Tools.byteToString(bytes, 0, sizeOfTheKeyValue));
		result.setPageNumber((int)bytes[sizeOfTheKeyValue]);
		result.setRecordNumber((int)bytes[sizeOfTheKeyValue+1]);
		return result;
	}
	@Override
	public IndexPage byteToPage(int pageNumber) throws IOException{//Parse the byte array to an index page object
		IndexPage result = new IndexPage(tableRecord);
		ArrayList<IndexRecord> listOfRecords = new ArrayList<IndexRecord>();
		byte[] pageInByte = new byte[512];
		raf.seek(pageNumber*512);
		raf.read(pageInByte);
		result.setMaxKeyValue(Tools.byteToString(pageInByte, 0, sizeOfTheKeyValue));
		result.setNumberOfAvailableRecords(pageInByte[sizeOfTheKeyValue]);
		for(int i=0;i<totalIndexRecord-result.getNumberOfAvailableRecords();i++){
			byte[] recordInBytes = new byte[sizeOfAnIndexRecord];
			System.arraycopy(pageInByte, sizeOfTheKeyValue+1+i*sizeOfAnIndexRecord, recordInBytes, 0, recordInBytes.length);
			IndexRecord record = byteToRecord(recordInBytes);
			listOfRecords.add(record);
		}
		result.setIndexRecords(listOfRecords);
		return result;
	}
	public void writeThePage(int pageNumber, IndexPage thePage) throws IOException {//Writes the page object to the given pageNumber of the file
		raf.seek(pageNumber*512);
		raf.write(thePage.toByte());
	}
}
